package fi.tikesos.validator.jena;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.RDFReader;
import com.hp.hpl.jena.rdf.model.RDFErrorHandler;
import com.hp.hpl.jena.rdf.model.RSIterator;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.rdf.model.impl.RDFReaderFImpl;
import com.hp.hpl.jena.reasoner.Reasoner;
import com.hp.hpl.jena.reasoner.rulesys.BuiltinRegistry;
import com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasonerFactory;
import com.hp.hpl.jena.util.PrintUtil;
import com.hp.hpl.jena.vocabulary.ReasonerVocabulary;

import fi.tikesos.validator.RDFValidator;
import fi.tikesos.validator.datatype.ValidationResult;
import fi.tikesos.validator.jena.builtin.CastAsBuiltin;
import fi.tikesos.validator.jena.builtin.IsNotCastableAsBuiltin;
import fi.tikesos.validator.jena.builtin.PropertyCountBuiltin;
import fi.tikesos.validator.jena.rdfa.RDFaReaderImpl;
import fi.tikesos.validator.jena.rdfa.XHTMLRDFaErrorHandler;

public class JenaRDFValidatorImpl implements RDFValidator {
	private OntModel schemaModel = null;
	private Reasoner reasoner = null;
	
	static {
		// Register BuiltIns
		BuiltinRegistry.theRegistry.register(new PropertyCountBuiltin());
		BuiltinRegistry.theRegistry.register(new IsNotCastableAsBuiltin());
		BuiltinRegistry.theRegistry.register(new CastAsBuiltin());

		// Register XHTML+RDFa reader
		RDFReaderFImpl.setBaseReaderClassName("XHTML+RDFa",
				RDFaReaderImpl.class.getName());

		// Register prefix for pretty output
		PrintUtil
				.registerPrefix("cwa", "http://sosmeta.fi/example/Closed.owl#");
	}

	/**
	 * @param schemaStream
	 */
	public JenaRDFValidatorImpl() {
		// Create empty model
		schemaModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM,
				null);
		reasoner = initializeReasoner();
	}

	/**
	 * @param content
	 * @param contentType
	 */
	public void addSchema(InputStream content, String contentType) {
		// Read schema to model
		schemaModel.read(content, null, contentType);
	}

	/**
	 * @return
	 */
	private static Reasoner initializeReasoner() {
		// Create a reasoner
		Model configurationModel = ModelFactory.createDefaultModel();
		Resource configuration = configurationModel.createResource();

		configuration.addProperty(ReasonerVocabulary.PROPruleMode, "forwardRETE");
		configuration.addProperty(ReasonerVocabulary.PROPruleSet,
				"rules/restriction.rules");
		configuration.addProperty(ReasonerVocabulary.PROPruleSet,
				"rules/cardinality.rules");
		configuration.addProperty(ReasonerVocabulary.PROPruleSet,
				"rules/range.rules");
		Reasoner reasoner = GenericRuleReasonerFactory.theInstance().create(
				configuration);
		return reasoner;
	}

	public ValidationResult validate(InputStream rdfDocumentStream,
			String documentType) {
		String result = "";
		Long x = System.currentTimeMillis();
		// Create model
		Model dataModel = ModelFactory.createNonreifyingModel();

		// Write down the results in RDF/XML form
		ValidationResult validationResult = new ValidationResult();
		
		
		try {
			// Inputstream to string
			StringBuffer content = new StringBuffer();
			byte[] inputBuffer = new byte[4096];
			for (int n; (n = rdfDocumentStream.read(inputBuffer)) != -1;) {
				content.append(new String(inputBuffer, 0, n));
			}
			String contentString = content.toString();
			validationResult.setXmlContent(contentString);
			// Convert string back to inputstream
			rdfDocumentStream = new ByteArrayInputStream(contentString.getBytes("UTF-8"));
			
			if ("XHTML+RDFa".compareToIgnoreCase(documentType) == 0) {
				// XHTML/RDFa
				RDFReader reader = dataModel.getReader("XHTML+RDFa");
				XHTMLRDFaErrorHandler errHandler = new XHTMLRDFaErrorHandler();
				reader.setErrorHandler(errHandler);
				reader.read(dataModel, rdfDocumentStream, "http://foo.bar.com/");
				
				
				
				if(errHandler.isError()) {
					validationResult.setStatusCode("FAILED");
					validationResult.setErrorGraph(errHandler.getErrorType()+" at "+errHandler.getErrorMessage());
					return validationResult;
				}
				} else {
				// Otherwise XML/RDF
				dataModel.read(rdfDocumentStream, "http://foo.bar.com/", documentType);
			}
			
			//dataModel.write(System.out);
			
			result+="XHTML+RDFa parsed in ";
			long y = System.currentTimeMillis();
			result+=(y-x);
			result+=" milliseconds";
			
			// Create INF model
			InfModel infModel = ModelFactory.createInfModel(reasoner, schemaModel, dataModel);
	
//		infModel.prepare();
//			infModel.write(System.out, "N-TRIPLE");
			
			result+="\nInferencing model created in ";
			y = System.currentTimeMillis();
			result+=(y-x);
			result+=" milliseconds\n";
	
			Property rdftype = schemaModel
					.getProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
			Property subclassof = schemaModel
					.getProperty("http://www.w3.org/2000/01/rdf-schema#subClassOf");
			Property sub = infModel
					.getProperty("http://sosmeta.fi/example/Closed.owl#subject");
			Property pro = infModel
					.getProperty("http://sosmeta.fi/example/Closed.owl#property");
			Property obj = infModel
					.getProperty("http://sosmeta.fi/example/Closed.owl#object");
			Resource failure = schemaModel
					.getResource("http://sosmeta.fi/example/Closed.owl#Failure");
			StmtIterator i = infModel.listStatements((Resource) null, subclassof,
					failure);
			
			int resulttest = result.length();
			
			
			int counter = 1;
			while (i.hasNext()) {
				Statement stmt = i.nextStatement();
				Resource subject = stmt.getSubject(); // get the subject
				
				
				// Iterate trough all failures
				ResIterator j = infModel
						.listResourcesWithProperty(rdftype, subject);
				while (j.hasNext()) {
					Resource err = j.nextResource();
					
					
					Statement subjectStatement = err.getProperty(sub);
					Statement propertyStatement = err.getProperty(pro);
	
					StmtIterator propertyList, objectList;
	
					// Iterate trough failure types
					propertyList = err.listProperties();
					
					//result+="\n"+ PrintUtil.print("Failuretype: " + subject.getLocalName());
					while (propertyList.hasNext()) {
						
						Statement current = propertyList.nextStatement();
						result+="\n Failure "+counter+": "+PrintUtil.print(current.getPredicate().getLocalName()+" -> "+current.getObject().toString());

						if(!propertyList.hasNext()){counter++;}
					} 
					
					// Iterate trough all statements about objects
					objectList = err.listProperties(obj);
					while (objectList.hasNext()) {
						
	
						RDFNode objectNode = objectList.nextStatement().getObject();
	
						// Makes actual statement that is used for finding reified
						// statements
						Statement actualStatement = dataModel.createStatement(
								subjectStatement.getObject().asResource(),
								dataModel.getProperty(propertyStatement.getObject()
										.toString()), objectNode);
	
						// Iterate trough reified statements
						RSIterator reifiedList = actualStatement
								.listReifiedStatements();
						while (reifiedList.hasNext()) {
							// Iterate trough properties of reified statement
							propertyList = reifiedList.nextRS().listProperties();
							while (propertyList.hasNext()) {
								Statement current = propertyList.nextStatement();
								result+="\n Failure "+(counter-1)+": position -> " + PrintUtil.print(current.getLiteral().getValue());
							}
						}
						
	
					}
					result+="\n";
				}
			}
			
			
			
			if(resulttest==result.length()) validationResult.setStatusCode("SUCCESS");
			else validationResult.setStatusCode("FAILED");
			
			
			result+="\nValidation ready in ";
			y = System.currentTimeMillis();
			result+=(y-x);
			result+=" milliseconds";
			
		System.out.println(result);
			validationResult.setErrorGraph(result);
		} catch (Exception exception) {
			
			result+="\nValidation failed in ";
			long y = System.currentTimeMillis();
			result+=(y-x);
			result+=" milliseconds";
			
			// Write exception to validation result
			validationResult.setStatusCode("FATAL");
			validationResult.setErrorGraph(result+":\n\n"+exception.toString());
		}
		
		return validationResult;
	}
}
